﻿using System;


namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Describes a business rule and indicates its status.
    /// </summary>
    [Serializable]
    public sealed class BusinessRule
    {
        #region Fields.

        string name;
        FormattedMessage description;
        string[] affectedFields;
        bool isObeyed = true;
        string errorCode = null;
        object businessObject;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the <b>BusinessRule</b> class.
        /// </summary>
        public BusinessRule()
        { }

        /// <summary>
        /// Initializes a new instance of the <b>BusinessRule</b> class.
        /// </summary>
        /// <param name="name">Name of the rule. Recommendation: the name should uniquelly identify the rule.</param>
        /// <param name="description">Friendly description of the rule for the client.</param>
        /// <param name="affectedFields">Array of business object's fields which are affected by this rule.</param>
        public BusinessRule(string name, FormattedMessage description, params string[] affectedFields)
        {
            this.name = name;
            this.description = description;
            this.affectedFields = affectedFields;
        }

        /// <summary>
        /// Initializes a new instance of the <b>BusinessRule</b> class.
        /// </summary>
        /// <param name="name">Name of the rule. Recommendation: the name should uniquelly identify the rule.</param>
        /// <param name="isObeyed">A value indicating whether the rule is obeyed or broken.</param>
        /// <param name="description">Friendly description of the rule for the client.</param>
        /// <param name="affectedFields">Array of business object's fields which are affected by this rule.</param>
        public BusinessRule(string name, bool isObeyed, FormattedMessage description, params string[] affectedFields)
        {
            this.name = name;
            this.isObeyed = isObeyed;
            this.description = description;
            this.affectedFields = affectedFields;
        }

        /// <summary>
        /// Initializes a new instance of the <b>BusinessRule</b> class.
        /// </summary>
        /// <param name="name">Name of the rule. The name must uniquely identify the rule in the provided <b>descriptionSource</b>.</param>
        /// <param name="isObeyed">A value indicating whether the rule is obeyed or broken.</param>
        /// <param name="descriptionSource"><see cref="ITextSource"/> from which a friendly description of the rule
        /// for the client is retrieved from. Must containg a message key equal to the provided rule <b>name</b>.</param>
        /// <param name="affectedFields">Array of business object's fields which are affected by this rule.</param>
        public BusinessRule(string name, bool isObeyed, ITextSource descriptionSource, params string[] affectedFields)
        {
            this.name = name;
            this.isObeyed = isObeyed;
            this.description = descriptionSource.GetString(name);
            this.affectedFields = affectedFields;
        }

        /// <summary>
        /// Initializes a new instance of the <b>BusinessRule</b> class.
        /// </summary>
        /// <param name="name">Name of the rule. The name must uniquely identify the rule in the provided <b>descriptionSource</b>.</param>
        /// <param name="isObeyed">A value indicating whether the rule is obeyed or broken.</param>
        /// <param name="bizObject">A business object whose <b>ISelfValidatingObject.MessageSource</b> property is used to retrieve 
        /// a friendly description of the rule for the client. Must containg a message key equal to the provided rule <b>name</b>.</param>
        /// <param name="affectedFields">Array of business object's fields which are affected by this rule.</param>
        public BusinessRule(string name, bool isObeyed, ISelfValidatingObject bizObject, params string[] affectedFields)
        {
            this.name = name;
            this.isObeyed = isObeyed;
            this.description = bizObject.MessageSource.GetString(name);
            this.affectedFields = affectedFields;
        }

        #endregion

        #region AffectsField.

        /// <summary>
        /// Checks whether the current rule affects the specified object field.
        /// </summary>
        /// <param name="field">Name of the business object's field, property, member to check.</param>
        /// <returns><b>true</b> if the rule affects the given field; <b>false</b> otherwise.</returns>
        public bool AffectsField(string field)
        {
            for (int i = 0; i < this.affectedFields.Length; i++)
                if (this.affectedFields[i] == field)
                    return true;

            return false;
        }

        #endregion

        #region AddField.

        /// <summary>
        /// Adds a new field to the list of affected fields.
        /// </summary>
        /// <param name="field">Field to add.</param>
        public void AddField(string field)
        {
            string[] newFields = new string[this.affectedFields.Length + 1];
            for (int i = 0; i < this.affectedFields.Length; i++)
                newFields[i] = this.affectedFields[i];

            newFields[newFields.Length - 1] = field;

            this.affectedFields = newFields;
        }

        #endregion

        #region ToString.

        /// <summary>
        /// Returns the formatted rule description.
        /// </summary>
        /// <returns><b>Description</b> converted to a string.</returns>
        public override string ToString()
        {
            return ToString(null);
        }

        /// <summary>
        /// Returns the formatted rule description.
        /// </summary>
        /// <param name="formatter">The <b>IMessageFormatter</b> that formattes the values contained in the <b>Description</b>.</param>
        /// <returns><b>Description</b> converted to a string.</returns>
        public string ToString(IMessageFormatter formatter)
        {
            string s;

            if (this.description != null)
            {
                if (formatter != null)
                    s = this.description.ToString(formatter);
                else
                    s = this.description.ToString();
            }
            else
            {
                s = "";
            }

            return s;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets the name of the rule.
        /// </summary>
        /// <remarks>Recommendation: the name should uniquelly identify the rule.
        /// Eg. [business class name].[method which checks the rule]</remarks>
        public string Name
        {
            get { return this.name; }
            set { this.name = value; }
        }

        /// <summary>
        /// Gets or sets the friendly description of the rule for the client.
        /// </summary>
        /// <remarks>This is the message which is displayed to user in the UI layer.</remarks>
        public FormattedMessage Description
        {
            get { return this.description; }
            set { this.description = value; }
        }

        /// <summary>
        /// Gets or sets the array of business object's fields which are affected by this rule.
        /// </summary>
        /// <remarks><para>If the array is <b>null</b> or empty then the doesn't affect a specific field, 
        /// but entire business object.</para>
        /// <para>If the array holds exactly one element then the rule affects only the specified field.
        /// Eg. it states that the field "Years" is not nullable and that it should be greated then 18.</para>
        /// <para>If the array contains more than element the the rule affects a combination of fields.
        /// Eg. it states even though both "Name" and "Tag" fields are nullable, at least one of them must be set.</para></remarks>
        public string[] AffectedFields
        {
            get { return this.affectedFields; }
            set { this.affectedFields = value; }
        }

        /// <summary>
        /// Gets or sets the unique error code of the rule. <b>Nullable</b>.
        /// </summary>
        /// <remarks>This might be usefull when rule descriptions cannot be generated in the business logic layer.
        /// In that case, the UI layer might decide how to describe the rule based on the unique error code.</remarks>
        public string ErrorCode
        {
            get { return this.errorCode; }
            set { this.errorCode = value; }
        }

        /// <summary>
        /// Gets or sets the value indicating whether the rule is obeyed in the object that generated it.
        /// </summary>
        public bool IsObeyed
        {
            get { return this.isObeyed; }
            set { this.isObeyed = value; }
        }

        /// <summary>
        /// Gets or sets the value indicating whether the rule is broken in the object that generated it.
        /// </summary>
        public bool IsBroken
        {
            get { return !this.isObeyed; }
            set { this.isObeyed = !value; }
        }

        /// <summary>
        /// Gets or sets the business object reffered by the current business rule. <b>Nullable</b>.
        /// </summary>
        public object BusinessObject
        {
            get { return this.businessObject; }
            set { this.businessObject = value; }
        }

        #endregion
    }
}